/*-------------------------------------------------------------------------
 *
 * sysv_sema.c
 *	  使用 SysV 信号量设施实现 PGSemaphores
 *
 *
 * Portions Copyright (c) 1996-2022, PostgreSQL Global Development Group
 * Portions Copyright (c) 1994, Regents of the University of California
 *
 * IDENTIFICATION
 *	  src/backend/port/sysv_sema.c
 *
 *-------------------------------------------------------------------------
 */
#include "postgres.h"

#include <signal.h>
#include <unistd.h>
#include <sys/file.h>
#include <sys/stat.h>
#ifdef HAVE_SYS_IPC_H
#include <sys/ipc.h>
#endif
#ifdef HAVE_SYS_SEM_H
#include <sys/sem.h>
#endif

#include "miscadmin.h"
#include "storage/ipc.h"
#include "storage/pg_sema.h"
#include "storage/shmem.h"


typedef struct PGSemaphoreData
{
	int			semId;			/* 信号量集合标识符 */
	int			semNum;			/* 集合内的信号量编号 */
} PGSemaphoreData;

#ifndef HAVE_UNION_SEMUN
union semun
{
	int			val;
	struct semid_ds *buf;
	unsigned short *array;
};
#endif

typedef key_t IpcSemaphoreKey;	/* 传递给semget(2)的信号量键 */
typedef int IpcSemaphoreId;		/* semget(2)返回的信号量ID */

/*
 * SEMAS_PER_SET是我们分配的每个信号量集合中有用信号量的数量。
 * 它必须小于您内核的SEMMSL（每个集合最大信号量）参数，这个值通常约为25。
 * （小于，因为我们在每个集合中额外分配一个信号量用于标识。）
 */
#define SEMAS_PER_SET	16

#define IPCProtection	(0600)	/* 仅用户可访问/修改 */

#define PGSemaMagic		537		/* 必须小于SEMVMX */


static PGSemaphore sharedSemas; /* 共享内存中的 PGSemaphoreData 数组 */
static int	numSharedSemas;		/* 到目前为止使用的PGSemaphoreDatas数量 */
static int	maxSharedSemas;		/* PGSemaphoreData数组的分配大小 */
static IpcSemaphoreId *mySemaSets;	/* 目前已获取的信号量集的 ID */
/* 目前已获取的信号量集的数量 */
/* mySemaSet 数组的分配大小 */
/*
 * 报告所需的共享内存量用于信号量
 */
/* 在 Windows 上不需要共享内存 */
/*
 * PGReserveSemaphores --- 初始化信号量支持
 *
 * 在 Win32 实现中，我们按需获取信号量；maxSemas 参数仅用于确定用于跟踪
 * 后续释放的已获取信号量的数组大小。我们使用匿名信号量，
 * 因此信号量在最后一个引用进程退出时会自动释放。
 */
/*
 * 在关闭或共享内存重新初始化时释放信号量
 *
 * （作为 on_shmem_exit 回调调用，因此参数列表比较奇怪）
 */
/*
 * PGSemaphoreCreate
 *
 * 分配一个初始计数为 1 的 PGSemaphore 结构
 */
/* 无法在后端做到这一点，因为静态状态属于主进程 */ 
/* 我们不需要命名信号量 */ 
/* 成功完成 */ 
/*
 * PGSemaphoreReset
 *
 * 将先前初始化的 PGSemaphore 重置为计数 0
 */
/*
	 * 在 Win32 中没有直接的 API，所以我们必须通过重复的 trylock
	 * 将信号量的计数降到 0。
	 */
  
/* 循环 */ 
/*
 * PGSemaphoreLock
 *
 * 锁定一个信号量（减少计数），如果计数 < 0 则阻塞。
 */
/*
	 * 注意：pgwin32_signal_event 应该首先执行，以确保在多个事件被设置时
	 * 会被报告。我们希望确保待处理的信号得到处理。
	 */
  
/*
	 * 像 PGSemaphoreLock 的其他实现一样，我们需要每次循环时检查取消/退出中断。
	 * 但在这里，没有关于系统调用是否会内部处理信号的隐藏魔法——我们自己处理。
	 */
  
/* 信号事件已被设置 - 我们有一个信号要发送 */ 
/* 我们得到了它！ */ 
/*
				 * 系统中断了等待，以执行此线程中的 I/O
				 * 完成例程或异步过程调用。PostgreSQL 并不引发这两种情况，
				 * 但不典型的加载的 DLL 或甚至其他进程可能会这样做。
				 * 现在，继续等待。
				 */
  
/*
 * PGSemaphoreUnlock
 *
 * 解锁一个信号量（增加计数）
 */
/*
 * PGSemaphoreTryLock
 *
 * 仅在能够不阻塞的情况下锁定信号量
 */
/* 我们得到了它！ */ 
/* 无法获取 */ 
/* 否则我们会有麻烦 */ 
/* 保持编译器安静 */
static int	numSemaSets;		/* 到目前为止获得的信号量集合的数量 */
static int	maxSemaSets;		/* mySemaSets数组的分配大小 */
static IpcSemaphoreKey nextSemaKey; /* 下一个尝试使用的键 */
static int	nextSemaNumber;		/* 上一个信号量集合中的下一个空闲信号量编号 */


static IpcSemaphoreId fc_InternalIpcSemaphoreCreate(IpcSemaphoreKey fc_semKey,
												 int fc_numSems);
static void fc_IpcSemaphoreInitialize(IpcSemaphoreId fc_semId, int fc_semNum,
								   int fc_value);
static void fc_IpcSemaphoreKill(IpcSemaphoreId fc_semId);
static int	fc_IpcSemaphoreGetValue(IpcSemaphoreId fc_semId, int fc_semNum);
static pid_t fc_IpcSemaphoreGetLastPID(IpcSemaphoreId fc_semId, int fc_semNum);
static IpcSemaphoreId fc_IpcSemaphoreCreate(int fc_numSems);
static void fc_ReleaseSemaphores(int fc_status, Datum fc_arg);


/*
 * InternalIpcSemaphoreCreate
 *
 * 尝试使用指定的键创建一个新的信号量集合。
 * 如果该集合已经存在，将失败（返回-1）。
 *
 * 如果我们以除与现有集合冲突外的错误代码失败，
 * 则打印出错误并中止。其他类型的错误表示不可恢复的问题。
 */
static IpcSemaphoreId
fc_InternalIpcSemaphoreCreate(IpcSemaphoreKey fc_semKey, int fc_numSems)
{
	int			fc_semId;

	fc_semId = semget(fc_semKey, fc_numSems, IPC_CREAT | IPC_EXCL | IPCProtection);

	if (fc_semId < 0)
	{
		int			fc_saved_errno = errno;

		/*
		 * 如果错误指示与现有集合冲突，则悄悄失败。
		 * 考虑到我们说了IPC_EXCL，可以预期会得到EEXIST，
		 * 但也许我们会得到权限违规？此外，如果旧集合正在被销毁但尚未消失，
		 * 可能会发生EIDRM。
		 */
		if (fc_saved_errno == EEXIST || fc_saved_errno == EACCES
#ifdef EIDRM
			|| fc_saved_errno == EIDRM
#endif
			)
			return -1;

		/*
		 * 否则抱怨并中止
		 */
		ereport(FATAL,
				(errmsg("could not create semaphores: %m"),
				 errdetail("Failed system call was semget(%lu, %d, 0%o).",
						   (unsigned long) fc_semKey, fc_numSems,
						   IPC_CREAT | IPC_EXCL | IPCProtection),
				 (fc_saved_errno == ENOSPC) ?
				 errhint("This error does *not* mean that you have run out of disk space.  "
						 "It occurs when either the system limit for the maximum number of "
						 "semaphore sets (SEMMNI), or the system wide maximum number of "
						 "semaphores (SEMMNS), would be exceeded.  You need to raise the "
						 "respective kernel parameter.  Alternatively, reduce PostgreSQL's "
						 "consumption of semaphores by reducing its max_connections parameter.\n"
						 "The PostgreSQL documentation contains more information about "
						 "configuring your system for PostgreSQL.") : 0));
	}

	return fc_semId;
}

/*
 * 将信号量初始化为指定的值。
 */
static void fc_IpcSemaphoreInitialize(IpcSemaphoreId fc_semId, int fc_semNum, int fc_value)
{
	union semun fc_semun;

	fc_semun.val = fc_value;
	if (semctl(fc_semId, fc_semNum, SETVAL, fc_semun) < 0)
	{
		int			fc_saved_errno = errno;

		ereport(FATAL,
				(errmsg_internal("semctl(%d, %d, SETVAL, %d) failed: %m",
								 fc_semId, fc_semNum, fc_value),
				 (fc_saved_errno == ERANGE) ?
				 errhint("You possibly need to raise your kernel's SEMVMX value to be at least "
						 "%d.  Look into the PostgreSQL documentation for details.",
						 fc_value) : 0));
	}
}

/*
 * IpcSemaphoreKill(semId) - 移除信号量集合
 */
static void fc_IpcSemaphoreKill(IpcSemaphoreId fc_semId)
{
	union semun fc_semun;

	fc_semun.val = 0;				/* 未使用，但保持编译器安静 */

	if (semctl(fc_semId, 0, IPC_RMID, fc_semun) < 0)
		elog(LOG, "semctl(%d, 0, IPC_RMID, ...) failed: %m", fc_semId);
}

/* 获取信号量的当前值（semval） */
static int fc_IpcSemaphoreGetValue(IpcSemaphoreId fc_semId, int fc_semNum)
{
	union semun fc_dummy;			/* 适用于Solaris */

	fc_dummy.val = 0;				/* 未使用 */

	return semctl(fc_semId, fc_semNum, GETVAL, fc_dummy);
}

/* 获取最后一个对信号量执行semop()的进程的PID */
static pid_t fc_IpcSemaphoreGetLastPID(IpcSemaphoreId fc_semId, int fc_semNum)
{
	union semun fc_dummy;			/* 适用于Solaris */

	fc_dummy.val = 0;				/* 未使用 */

	return semctl(fc_semId, fc_semNum, GETPID, fc_dummy);
}


/*
 * 创建一个具有给定数量有用信号量的信号量集合
 * （实际上分配一个额外的信号量作为标识符）。
 * 如果找到死亡的Postgres信号量集合，则进行回收，但
 * 对于与非Postgres信号量集合的冲突，我们不会失败。
 *
 * 这里的想法是检测并重用可能已被崩溃的postmaster或后端分配的键。
 */
static IpcSemaphoreId
fc_IpcSemaphoreCreate(int fc_numSems)
{
	IpcSemaphoreId fc_semId;
	union semun fc_semun;
	PGSemaphoreData fc_mysema;

	/* 循环直到找到一个空闲的IPC键 */
	for (nextSemaKey++;; nextSemaKey++)
	{
		pid_t		fc_creatorPID;

		/* 尝试创建新的信号量集合 */
		fc_semId = fc_InternalIpcSemaphoreCreate(nextSemaKey, fc_numSems + 1);
		if (fc_semId >= 0)
			break;				/* 创建成功 */

		/* 查看它是否看起来是来自死去的Postgres进程的残留物 */
		fc_semId = semget(nextSemaKey, fc_numSems + 1, 0);
		if (fc_semId < 0)
			continue;			/* 失败：必须是其他应用程序的 */
		if (fc_IpcSemaphoreGetValue(fc_semId, fc_numSems) != PGSemaMagic)
			continue;			/* 信号量属于非Postgres应用程序 */

		/*
		 * 如果创建者PID是我自己的PID，或者不属于任何现存的
		 * 进程，那么安全地销毁它。
		 */
		fc_creatorPID = fc_IpcSemaphoreGetLastPID(fc_semId, fc_numSems);
		if (fc_creatorPID <= 0)
			continue;			/* 糟糕，GETPID失败 */
		if (fc_creatorPID != getpid())
		{
			if (kill(fc_creatorPID, 0) == 0 || errno != ESRCH)
				continue;		/* 信号量属于一个活动进程 */
		}

		/*
		 * sema 集合似乎来自一个已死的 Postgres 进程，或者来自该进程的
		 * 之前生命周期的循环。若可能，请将其清除。
		 * 这通常不应该失败，但如果失败，假设该 sema 集合
		 * 最终属于其他人，并安静地继续。
		 */
		fc_semun.val = 0;			/* 未使用，但保持编译器安静 */
		if (semctl(fc_semId, 0, IPC_RMID, fc_semun) < 0)
			continue;

		/*
		 * 现在再次尝试创建 sema 集合。
		 */
		fc_semId = fc_InternalIpcSemaphoreCreate(nextSemaKey, fc_numSems + 1);
		if (fc_semId >= 0)
			break;				/* 创建成功 */

		/*
		 * 只有在某个其他进程在我们之前成功创建了相同
		 * sema 键时，我们才能到达这里。让他拥有那个，循环尝试
		 * 下一个键。
		 */
	}

	/*
	 * 好吧，我们创建了一个新的 sema 集合。将其标记为由该进程创建。
	 * 我们通过将备用信号量设置为 PGSemaMagic-1，然后
	 * 用 semop() 增加它来做到这一点。这样留下的值为 PGSemaMagic
	 * 和 sempid 关联到该进程。
	 */
	fc_IpcSemaphoreInitialize(fc_semId, fc_numSems, PGSemaMagic - 1);
	fc_mysema.semId = fc_semId;
	fc_mysema.semNum = fc_numSems;
	PGSemaphoreUnlock(&fc_mysema);

	return fc_semId;
}


/*
 * 报告信号量所需的共享内存量
 */
Size PGSemaphoreShmemSize(int fc_maxSemas)
{
	return mul_size(fc_maxSemas, sizeof(PGSemaphoreData));
}

/*
 * PGReserveSemaphores --- 初始化信号量支持
 *
 * 这在邮政主管启动或共享内存重新初始化期间被调用。
 * 它应该执行所需的所有操作，以便能够支持最多 maxSemas
 * 后续的 PGSemaphoreCreate 调用。此外，如果在这里或在
 * PGSemaphoreCreate 中获取了任何系统资源，请注册一个 on_shmem_exit
 * 回调以释放它们。
 *
 * 在 SysV 实现中，我们按需获取信号量集合；
 * maxSemas 参数仅用于调整数组大小。在共享内存中有一个
 * PGSemaphoreData 结构的数组，并且有一个每个 SysV 信号量集合的
 * postmaster 本地数组，用于在完成时释放信号量集合。
 * （这种设计确保了 postmaster 关闭不会依赖于共享内存的内容，
 * 因为失败的后端可能已经破坏了它。）
 */
void PGReserveSemaphores(int fc_maxSemas)
{
	struct stat fc_statbuf;

	/*
	 * 我们使用数据目录的 inode 号来为寻找空闲
	 * 信号量键提供种子。这最小化了与其他
	 * 主进程冲突的几率，同时最大化了我们检测和清理
	 * 残留在自己目录中的崩溃主进程留下的信号量的几率。
	 */
	if (stat(DataDir, &fc_statbuf) < 0)
		ereport(FATAL,
				(errcode_for_file_access(),
				 errmsg("could not stat data directory \"%s\": %m",
						DataDir)));

	/*
	 * 我们必须使用 ShmemAllocUnlocked()，因为保护
	 * ShmemAlloc() 的自旋锁还未准备好。（当我们
	 * 用信号量模拟自旋锁时需要这种顺序。）
	 */
	sharedSemas = (PGSemaphore)
		ShmemAllocUnlocked(PGSemaphoreShmemSize(fc_maxSemas));
	numSharedSemas = 0;
	maxSharedSemas = fc_maxSemas;

	maxSemaSets = (fc_maxSemas + SEMAS_PER_SET - 1) / SEMAS_PER_SET;
	mySemaSets = (IpcSemaphoreId *)
		malloc(maxSemaSets * sizeof(IpcSemaphoreId));
	if (mySemaSets == NULL)
		elog(PANIC, "out of memory");
	numSemaSets = 0;
	nextSemaKey = fc_statbuf.st_ino;
	nextSemaNumber = SEMAS_PER_SET; /* 在第一次调用时强制分配 sema 集合 */

	on_shmem_exit(fc_ReleaseSemaphores, 0);
}

/*
 * 在关闭或共享内存重新初始化时释放信号量
 *
 * （作为 on_shmem_exit 回调被调用，因此参数列表有点奇怪）
 */
static void fc_ReleaseSemaphores(int fc_status, Datum fc_arg)
{
	int			fc_i;

	for (fc_i = 0; fc_i < numSemaSets; fc_i++)
		fc_IpcSemaphoreKill(mySemaSets[fc_i]);
	free(mySemaSets);
}

/*
 * PGSemaphoreCreate
 *
 * 分配一个初值为1的PGSemaphore结构
 */
PGSemaphore PGSemaphoreCreate(void)
{
	PGSemaphore fc_sema;

	/* 在后端无法执行此操作，因为静态状态属于主进程 */
	Assert(!IsUnderPostmaster);

	if (nextSemaNumber >= SEMAS_PER_SET)
	{
		/* 是时候分配另一组信号量了 */
		if (numSemaSets >= maxSemaSets)
			elog(PANIC, "too many semaphores created");
		mySemaSets[numSemaSets] = fc_IpcSemaphoreCreate(SEMAS_PER_SET);
		numSemaSets++;
		nextSemaNumber = 0;
	}
	/* 使用下一个共享的 PGSemaphoreData */
	if (numSharedSemas >= maxSharedSemas)
		elog(PANIC, "too many semaphores created");
	fc_sema = &sharedSemas[numSharedSemas++];
	/* 分配当前集合中的下一个空闲信号量 */
	fc_sema->semId = mySemaSets[numSemaSets - 1];
	fc_sema->semNum = nextSemaNumber++;
	/* 将其初始化为计数 1 */
	fc_IpcSemaphoreInitialize(fc_sema->semId, fc_sema->semNum, 1);

	return fc_sema;
}

/*
 * PGSemaphoreReset
 *
 * 将先前初始化的PGSemaphore重置为计数0
 */
void PGSemaphoreReset(PGSemaphore fc_sema)
{
	fc_IpcSemaphoreInitialize(fc_sema->semId, fc_sema->semNum, 0);
}

/*
 * PGSemaphoreLock
 *
 * 锁定一个信号量（减少计数），如果计数会变为< 0则阻塞
 */
void PGSemaphoreLock(PGSemaphore fc_sema)
{
	int			fc_errStatus;
	struct sembuf fc_sops;

	fc_sops.sem_op = -1;			/* 递减 */
	fc_sops.sem_flg = 0;
	fc_sops.sem_num = fc_sema->semNum;

	/*
	 * 注意：如果 errStatus 是 -1 且 errno == EINTR，则意味着我们因为接收到信号
	 * 而过早从操作中返回。因此我们尝试再次锁定信号量。
	 *
	 * 我们曾经在这里检查中断，但那需要直接从信号处理程序服务中断。
	 * 这很难安全且可移植地做到。
	 */
	do
	{
		fc_errStatus = semop(fc_sema->semId, &fc_sops, 1);
	} while (fc_errStatus < 0 && errno == EINTR);

	if (fc_errStatus < 0)
		elog(FATAL, "semop(id=%d) failed: %m", fc_sema->semId);
}

/*
 * PGSemaphoreUnlock
 *
 * 解锁一个信号量（增加计数）
 */
void PGSemaphoreUnlock(PGSemaphore fc_sema)
{
	int			fc_errStatus;
	struct sembuf fc_sops;

	fc_sops.sem_op = 1;			/* 增加 */
	fc_sops.sem_flg = 0;
	fc_sops.sem_num = fc_sema->semNum;

	/*
	 * 注意：如果errStatus为-1且errno == EINTR，则意味着我们
	 * 因为收到了信号而提前从操作中返回。因此我们再次尝试解锁信号量。
	 * 目前不清楚这种情况是否会真的发生，但还是要做好应对。
	 */
	do
	{
		fc_errStatus = semop(fc_sema->semId, &fc_sops, 1);
	} while (fc_errStatus < 0 && errno == EINTR);

	if (fc_errStatus < 0)
		elog(FATAL, "semop(id=%d) failed: %m", fc_sema->semId);
}

/*
 * PGSemaphoreTryLock
 *
 * 仅在能够而不阻塞的情况下锁定信号量
 */
bool PGSemaphoreTryLock(PGSemaphore fc_sema)
{
	int			fc_errStatus;
	struct sembuf fc_sops;

	fc_sops.sem_op = -1;			/* 递减 */
	fc_sops.sem_flg = IPC_NOWAIT;	/* 但不阻塞 */
	fc_sops.sem_num = fc_sema->semNum;

	/*
	 * 注意：如果errStatus为-1且errno == EINTR，则意味着我们
	 * 因为收到了信号而提前从操作中返回。因此我们再次尝试锁定信号量。
	 */
	do
	{
		fc_errStatus = semop(fc_sema->semId, &fc_sops, 1);
	} while (fc_errStatus < 0 && errno == EINTR);

	if (fc_errStatus < 0)
	{
		/* 期望 EAGAIN 或 EWOULDBLOCK（平台相关） */
#ifdef EAGAIN
		if (errno == EAGAIN)
			return false;		/* 锁定失败 */
#endif
#if defined(EWOULDBLOCK) && (!defined(EAGAIN) || (EWOULDBLOCK != EAGAIN))
		if (errno == EWOULDBLOCK)
			return false;		/* 锁定失败 */
#endif
		/* 否则我们会有麻烦 */
		elog(FATAL, "semop(id=%d) failed: %m", fc_sema->semId);
	}

	return true;
}
